CMS Compliance

Implementations MUST support key transport, key agreement, and previously distributed symmetric key-encryption keys, as represented by ktri, kari, and kekri, respectively.

Implementations MAY support the password-based key management as represented by pwri. Implementations MAY support any other key management technique such as Boneh-Franklin and Boneh-Boyen Identity-Based Encryption (RFC 5409) or other SYNRC encryption techniques such as KYBER Key Transport variations (LAMPS-WG) for post-quantum cryptography (PQC).

IETF follow up (SMIME-WG): 5990, 5911, 5750--5754, 5652, 5408, 5409, 5275, 5126, 5035, 4853, 4490, 4262, 4134, 4056, 4010, 3850, 3851, 3852, 3854, 3855, 3657, 3560, 3565, 3537, 3394, 3369, 3370, 3274, 3114, 3278, 3218, 3211, 3217, 3183, 3185, 3125--3126, 3058, 2984, 2876, 2785, 2630, 2631, 2632, 2633, 5083, 5084, 2634.

Compatibility: Erlang SSL, LibreSSL CMS, OpenSSL CMS, GnuPG S/MIME.

Elixir.CMS.ebin

X.509 CMS Cryptographic Message Syntax specification for RSA (Key Transport), ECC (Key Agreement), KEK (Key Encryption Key) disciplines for Erlang/OTP never heartbleeded (!) CRYPTO and SSL applcations. Implemented as CMS module of CA applcation.

defmodule CMS do def decrypt(cms, {schemeOID, privateKeyBin}) do {_,{:ContentInfo,_,{:EnvelopedData,_,_,x,y,_}}} = cms {:EncryptedContentInfo,_,{_,encOID,{_,<<_::16,iv::binary>>}},data} = y case :proplists.get_value(:kari, x, []) do [] -> case :proplists.get_value(:ktri, x, []) do [] -> case :proplists.get_value(:kekri, x, []) do [] -> case :proplists.get_value(:pwri, x, []) do [] -> {:error, "Unknown Other Recepient Info"} pwri -> pwri(pwri, privateKeyBin, encOID, data, iv) end kekri -> kekri(kekri, privateKeyBin, encOID, data, iv) end ktri -> ktri(ktri, privateKeyBin, encOID, data, iv) end kari -> kari(kari, privateKeyBin, schemeOID, encOID, data, iv) end end end

CMS-KARI-ECC

IETF 3278:2002 Use of Elliptic Curve Cryptography (ECC) Algorithms in Cryptographic Message Syntax (CMS) with support of Suite B IETF 5008:2007, 6318:2011.

$ openssl cms -decrypt -in encrypted.txt -inkey client.key -recip client.pem $ openssl cms -encrypt -aes256 -in message.txt -out encrypted.txt \ -recip client.pem -keyopt ecdh_kdf_md:sha256
# CMS Codec KARI: ECC+KDF/ECB+AES/KW+256/CBC def map(:'dhSinglePass-stdDH-sha512kdf-scheme'), do: :sha512 def map(:'dhSinglePass-stdDH-sha384kdf-scheme'), do: :sha384 def map(:'dhSinglePass-stdDH-sha256kdf-scheme'), do: :sha256 def eccCMS(ukm, bit), do: :'CMSECCAlgs-2009-02'.encode(:'ECC-CMS-SharedInfo', sharedInfo(ukm, bit)) def sharedInfo(ukm, len), do: {:'ECC-CMS-SharedInfo', {:'KeyWrapAlgorithm',{2,16,840,1,101,3,4,1,45},:asn1_NOVALUE}, ukm, <>} def kari(kari, privateKeyBin, schemeOID, encOID, data, iv) do {_,:v3,{_,{_,_,publicKey}},ukm,{_,kdfOID,_},[{_,_,encryptedKey}]} = kari {scheme,_} = CA.ALG.lookup(schemeOID) {kdf,_} = CA.ALG.lookup(kdfOID) {enc,_} = CA.ALG.lookup(encOID) sharedKey = :crypto.compute_key(:ecdh,publicKey,privateKeyBin,scheme) {_,payload} = eccCMS(ukm, 256) derived = KDF.derive(map(kdf), sharedKey, 32, payload) unwrap = CA.AES.KW.unwrap(encryptedKey, derived) res = CA.AES.decrypt(enc, data, unwrap, iv) {:ok, res} end
def testDecryptECC(), do: CA.CMS.decrypt(testECC(), testPrivateKeyECC()) def testECC() do {:ok,base} = :file.read_file "priv/certs/encrypted.txt" [_,s] = :string.split base, "\n\n" x = :base64.decode s :'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x) end def testPrivateKeyECC() do privateKey = :public_key.pem_entry_decode(pem("priv/certs/client.key")) {:'ECPrivateKey',_,privateKeyBin,{:namedCurve,schemeOID},_,_} = privateKey {schemeOID,privateKeyBin} end

CMS-KEKRI-KEK

Key Encryption Key Recipient Info as defined by CMS IETF 5652:2009, 3852:2004, 3369:2002, 2630:1999.

$ openssl cms -encrypt -secretkeyid 07 \ -secretkey 0123456789ABCDEF0123456789ABCDEF \ -aes256 -in message.txt -out encrypted2.txt $ openssl cms -decrypt -in encrypted2.txt -secretkeyid 07 \ -secretkey 0123456789ABCDEF0123456789ABCDEF
# CMS Codec KEKRI: KEK+AES-KW+CBC def kekri(kekri, privateKeyBin, encOID, data, iv) do {:'KEKRecipientInfo',_vsn,_,{_,kea,_},encryptedKey} = kekri _ = CA.ALG.lookup(kea) {enc,_} = CA.ALG.lookup(encOID) unwrap = CA.AES.KW.unwrap(encryptedKey,privateKeyBin) res = CA.AES.decrypt(enc, data, unwrap, iv) {:ok, res} end
def testDecryptKEK(), do: CA.CMS.decrypt(testKEK(), testPrivateKeyKEK()) def testPrivateKeyKEK() do {:kek, :binary.decode_hex("0123456789ABCDEF0123456789ABCDEF")} end def testKEK() do {:ok,base} = :file.read_file "priv/certs/encrypted2.txt" [_,s] = :string.split base, "\n\n" x = :base64.decode s :'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x) end

CMS-KTRI-RSA

The very first CMS IETF 3852:1999.

$ gpgsm --list-keys $ gpgsm --list-secret-keys $ gpgsm -r 0xD3C8F78A -e CNAME > cms.bin $ gpgsm -u 0xD3C8F78A -d cms.bin $ gpgsm --export-secret-key-p12 0xD3C8F78A > key.bin $ openssl pkcs12 -in key.bin -nokeys -out public.pem $ openssl pkcs12 -in key.bin -nocerts -nodes -out private.pem
# CMS Codec KTRI: RSA+RSAES-OAEP def ktri(ktri, privateKeyBin, encOID, data, iv) do {:'KeyTransRecipientInfo',_vsn,_,{_,schemeOID,_},key} = ktri {:rsaEncryption,_} = CA.ALG.lookup schemeOID {enc,_} = CA.ALG.lookup(encOID) sessionKey = :public_key.decrypt_private(key, privateKeyBin) res = CA.AES.decrypt(enc, data, sessionKey, iv) {:ok, res} end
def testDecryptRSA(), do: CA.CMS.decrypt(testRSA(), testPrivateKeyRSA()) def testPrivateKeyRSA() do {:ok,bin} = :file.read_file("priv/rsa-cms.key") pki = :public_key.pem_decode(bin) [{:PrivateKeyInfo,_,_}] = pki rsa = :public_key.pem_entry_decode(hd(pki)) {:'RSAPrivateKey',:'two-prime',_n,_e,_d,_,_,_,_,_,_} = rsa {:rsaEncryption,rsa} end def testRSA() do {:ok,x} = :file.read_file "priv/rsa-cms.bin" :'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x) end

KDF

KDF (MD5: 128, SHA: 160—512) and HKDF (HMAC) Key Derive functions used in ECC CMS schemes as of NIST SP 800-108r1.

defmodule KDF do def hl(:md5), do: 16 def hl(:sha), do: 20 def hl(:sha224), do: 28 def hl(:sha256), do: 32 def hl(:sha384), do: 48 def hl(:sha512), do: 64 def derive(h, d, len, x) do :binary.part(:lists.foldr(fn i, a -> :crypto.hash(h, d <> <> <> x) <> a end, <<>>, :lists.seq(1,round(Float.ceil(len/hl(h))))), 0, len) end end

AES-KW

AES Key Wrap function is applicable to keys of 128/192/256 bit using AES-ECB encoding as of RFC 5649:2009 Advanced Encryption Standard (AES) Key Wrap with Padding Algorithm.

-define(MSB64, 1/unsigned-big-integer-unit:64). -define(DEFAULT_IV, << 16#A6A6A6A6A6A6A6A6:?MSB64 >>). unwrap(CipherText, KEK) -> unwrap(CipherText, KEK, ?DEFAULT_IV). unwrap(CipherText, KEK, IV) when (byte_size(CipherText) rem 8) =:= 0 andalso (bit_size(KEK) =:= 128 orelse bit_size(KEK) =:= 192 orelse bit_size(KEK) =:= 256) -> BlockCount = (byte_size(CipherText) div 8) - 1, IVSize = byte_size(IV), case do_unwrap(CipherText, 5, BlockCount, KEK) of << IV:IVSize/binary, PlainText/binary >> -> PlainText; _ -> erlang:error({badarg, [CipherText, KEK, IV]}) end. codec(128) -> aes_128_ecb; codec(192) -> aes_192_ecb; codec(256) -> aes_256_ecb. do_unwrap(Buffer, J, _BlockCount, _KEK) when J < 0 -> Buffer; do_unwrap(Buffer, J, BlockCount, KEK) -> do_unwrap(do_unwrap(Buffer, J, BlockCount, BlockCount, KEK), J - 1, BlockCount, KEK). do_unwrap(Buffer, _J, I, _BlockCount, _KEK) when I < 1 -> Buffer; do_unwrap(<< A0:?MSB64, Rest/binary >>, J, I, BlockCount, KEK) -> HeadSize = (I - 1) * 8, << Head:HeadSize/binary, B0:8/binary, Tail/binary >> = Rest, Round = (BlockCount * J) + I, A1 = A0 bxor Round, Data = << A1:?MSB64, B0/binary >>, << A2:8/binary, B1/binary >> = crypto:crypto_one_time(codec(bit_size(KEK)), KEK, ?DEFAULT_IV, Data, [{encrypt,false}]), do_unwrap(<< A2/binary, Head/binary, B1/binary, Tail/binary >>, J, I - 1, BlockCount, KEK).

AES-256

All AES-256 flavours are implemented for a wide range of ECC Key Agreement schemes.

def decrypt(crypto_codec, data, key, iv \\ :crypto.strong_rand_bytes(16)) def decrypt(:'id-aes256-ECB', data, key, iv), do: decryptAES256ECB(data, key, iv) def decrypt(:'id-aes256-CBC', data, key, iv), do: decryptAES256CBC(data, key, iv) def decrypt(:'id-aes256-GCM', data, key, iv), do: decryptAES256GCM(data, key, iv) def decrypt(:'id-aes256-CCM', data, key, iv), do: decryptAES256CCM(data, key, iv)
def test() do [ check_SECP384R1_GCM256(), check_X25519_GCM256(), check_C2PNB368w1_GCM256(), check_BrainPoolP512t1_GCM256(), check_BrainPoolP512t1_GCM256(), check_SECT571_GCM256(), check_X448_GCM256(), check_X448_CBC256(), check_X448_ECB256(), ] end